## CMake 的基础配置
#指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.30)
#声明 CMake 创建运行在目标系统上的文件，Generic：嵌入式环境
set(CMAKE_SYSTEM_NAME Generic)
#声明目标系统的版本
set(CMAKE_SYSTEM_VERSION 1)

##指定项目的交叉编译器和工具，需要配置GCC工具链的环境变量
#指定C编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
#指定C++编译器
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
#指定汇编语言编译器
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
#指定创建存档或创建static库的程序名称。
set(CMAKE_AR arm-none-eabi-ar)
#指定生成二进制文件的工具
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
#指定生成汇编文件的工具
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
#指定生成大小报告的工具
set(SIZE arm-none-eabi-size)
#跳过编译器可执行文件 -rdynamic 检查
#[[
    -rdynamic是一个链接选项，用于将所有符号导出到动态符号表，
        便于动态调试和堆栈回溯。
    当使用此选项时，程序在运行时可通过动态链接库访问主程序符号，
        但可能增加运行时间和文件大小。

    告诉 CMake 链接器生成的二进制文件不是为主机系统设计的，
        因此不会在主机系统上运行。
            当不包含此行时，CMake 将不会编译代码，声称编译器已损坏。
]]
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)


##项目设置
#定义项目的名称和使用的编程语言<C><CXX><汇编ASM>
project(Project  C CXX ASM)
#设置项目中 C 标准
set(CMAKE_C_STANDARD 11)
#[[
    set(CMAKE_CXX_STANDARD_REQUIRED OFF)
    用于指定是否强制使用 `CMAKE_CXX_STANDARD` 中设置的 C++ 版本。
    如果设置为 `ON`，那么 CMake 会要求编译器必须支持 
        `CMAKE_CXX_STANDARD` 中设置的 C++ 版本，否则会配置失败。
    如果设置为 `OFF` 或者未设置，那么如果编译器不支持 `CMAKE_CXX_STANDARD` 
        中设置的 C++ 版本，CMake 会回退到编译器支持的最新版本。
]]
#设置项目中 C++ 标准
set(CMAKE_CXX_STANDARD 17)

#[[
    出于低功耗、封装限制等种种原因，
        以前的一些ARM处理器没有独立的硬件浮点运算单元，
            需要用软件来实现浮点运算。
    随着技术发展，现在高端的ARM处理器基本都具备了硬件执行浮点操作的能力。 
    新旧两种架构之间的差异，就产生了两个不同的接口 – 软浮点与硬浮点。
    编译选项：
    -mfpu =name（neon or vfp）指定FPU 单元
        NEON 仅适用于向量[视频的编码/解码]，不支持双精度浮点（VFP 支持双精度），
            并且不支持平方根和除法等某些复杂运算。
        VFP 和 NEON 是共用浮点寄存器，只是执行的指令不同。
    -mfloat-abi= name（soft、hard、 softfp）：指定软件浮点或硬件浮点或 兼容软浮点调用接口
        在有fpu的情况下，可以通过-mfloat-abi来指定使用哪种，有如下三种值：
            **soft：**不用fpu计算，即使有fpu浮点运算单元也不用。
            armel：(arm eabi little endian) 也即softfp，用fpu计算（即会将浮点运算编译成对应的浮点指令），
                但是传参数用普通寄存器传，这样中断的时候，只需要保存普通寄存器，
                    中断负荷小，但是参数需要转换成浮点的再计算。
            armhf：(arm hard float)也即hard，用fpu计算，传参数用fpu中的浮点寄存器传，
                省去了转换性能最好，但是中断负荷高。
            而arm64，64位的arm默认就是hard float的，因此不需要hf的后缀。
                kernel、rootfs和app编译的时候，指定的必须保持一致才行。
            使用softfp模式，会存在不必要的浮点到整数、整数到浮点的转换。
                而使用hard模式，在每次浮点相关函数调用时，平均能节省20个CPU周期。
    ARM启用硬浮点运算:
        需要使用硬浮点，需要内核开启对硬浮点支持，同时编译时要使用上面的softfp或hard，才可以使用FPU/NEON进行计算。
]]
##硬件浮点数设置
#add_compile_definitions(ARM_MATH_CM4;ARM_MATH_MATRIX_CHECK;ARM_MATH_ROUNDING)
#add_compile_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16)
#add_link_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16)
##软件浮点数设置
#add_compile_options(-mfloat-abi=soft)

##添加编译选项
#[[
    add_compile_options 用于添加编译选项，
        -mcpu=cortex-m3 用于指定 CPU 架构，ARM内核有M0，M3，M4，M7，H7，A系列
        -mthumb 用于指定 Thumb 指令集，
        -mthumb-interwork 用于指定 Thumb 指令集兼容 ARM 指令集。
]]
add_compile_options(-mcpu=cortex-m3 -mthumb -mthumb-interwork)
#[[
    有时我们的程序会定义一些暂时使用不上的功能和函数，虽然我们不使用这些功能和函数，
        但它们往往会浪费我们的ROM和RAM的空间。这在使用静态库时，体现的更为严重。
            有时，我们只使用了静态库仅有的几个功能，但是系统默认会自动把整个静态库全部链接到可执行程序中，
                造成可执行程序的大小大大增加。
    为了解决前面分析的问题，我们引入了标题中的几个参数。
        GCC链接操作是以section作为最小的处理单元，只要一个section中的某个符号被引用，
            该section就会被加入到可执行程序中去。
    因此，GCC在编译时可以使用 -ffunction-sections和 -fdata-sections 将每个函数或符号创建为一个sections，
        其中每个sections名与function或data名保持一致。
    而在链接阶段， -Wl,–gc-sections 指示链接器去掉不用的section
        （其中-wl, 表示后面的参数 -gc-sections 传递给链接器），这样就能减少最终的可执行程序的大小了。
]]
add_compile_options(-ffunction-sections -fdata-sections -fno-common -fmessage-length=0)
#set(CMAKE_CFLAGS "-fdata-sections -ffunction-sections -Wl,--gc-sections")

##c++17绝对地址警告设置
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-register")

##Cmake 构建类型设置
#[[
    在CMake中，我们可以使用STREQUAL来进行字符串的相等性比较。
        如果两个字符串完全相同，那么STREQUAL会返回TRUE，否则返回FALSE。
    STRLESS和STRGREATER来进行字符串的大小比较(第一个字符串与第二个比较)。
    使用STRCMP来进行字符串的字典序比较。

    cmake支持四种构建类型：
        Debug；调试版本
        Release；正式版本
        RelWithDebInfo；既优化又能调试的版本
        MinSizeRel；最小体积版本
]]
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
    message(VERBOSE "Maximum optimization for speed")
    add_compile_options(-Ofast)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
    message(VERBOSE "Maximum optimization for speed, debug info included")
    add_compile_options(-Ofast -g)
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel")
    message(VERBOSE "Maximum optimization for size")
    add_compile_options(-Os)
else ()
    message(VERBOSE "Minimal optimization, debug info included")
    add_compile_options(-Og -g)
endif ()

# 预定义宏
add_definitions(
    -D STM32F10X_HD
    -D USE_STDPERIPH_DRIVER
)

# 链接脚本
set(LINK_SCRIPT 
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/STM32F103C8Tx_FLASH.ld
)
# 启动文件
set(STARTUP_SCRIPT
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/Startup/gcc_ride7/startup_stm32f10x_hd.s
)
# CMSIS 内核文件
set(CMSIS_CORE
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/Core/core_cm3.c
)
# 标准库外设源文件
file(GLOB STDPERIPH_DRIVER
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/Lib/src/*.c
)

# 用户程序的源文件（递归查找）
file(GLOB_RECURSE USER_SRC
    ${CMAKE_SOURCE_DIR}/System/delay/*.c
    ${CMAKE_SOURCE_DIR}/System/sys/*.c
    ${CMAKE_SOURCE_DIR}/System/usart/*.c
    ${CMAKE_SOURCE_DIR}/Hardware/Led/*.c
    ${CMAKE_SOURCE_DIR}/Hardware/Key/*.c
    ${CMAKE_SOURCE_DIR}/Hardware/My_Usart/*.c
    ${CMAKE_SOURCE_DIR}/Hardware/OLED/*.c
    ${CMAKE_SOURCE_DIR}/Hardware/Timer/*.c
    ${CMAKE_SOURCE_DIR}/User/*.c
)
# ELF 文件名称
set(ELF_TARGET ${CMAKE_PROJECT_NAME}.elf)

# 添加 ELF 目标文件
add_executable(
    ${ELF_TARGET}
    ${CMSIS_CORE}
    ${STARTUP_SCRIPT}
    ${STDPERIPH_DRIVER}
    ${USER_SRC}
)

# 头文件路径
target_include_directories(
    ${ELF_TARGET} PRIVATE
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/Core
    ${CMAKE_SOURCE_DIR}/STM32F10x_StdLib/Lib/inc
    ${CMAKE_SOURCE_DIR}/Libraries/STM32F10x_StdPeriph_Driver/inc
    ${CMAKE_SOURCE_DIR}/System/delay
    ${CMAKE_SOURCE_DIR}/System/sys
    ${CMAKE_SOURCE_DIR}/System/usart
    ${CMAKE_SOURCE_DIR}/Hardware/Led
    ${CMAKE_SOURCE_DIR}/Hardware/Key
    ${CMAKE_SOURCE_DIR}/Hardware/My_Usart
    ${CMAKE_SOURCE_DIR}/Hardware/OLED
    ${CMAKE_SOURCE_DIR}/Hardware/Timer
    ${CMAKE_SOURCE_DIR}/User
    
)

#[[
    # optimization    ## 选择优化等级，优化等级低->高，可参考Keil的设置，如果程序运行不理想，优化程序或降低优化等级
    #OPT = -O0        ## 没有优化。
    #OPT = -O1        ## 编译器试图减少代码大小和执行时间，而不执行任何花费大量编译时间的优化。
    #OPT = -Og        ## 参数 -Og 是在 -O1 的基础上，去掉了那些影响调试的优化告诉编译器，编译后的代码不要影响调试，但调试信息的生成还是靠 -g 参数的。
    #OPT = -O2        ## 更加优化。GCC执行几乎所有支持的优化，不涉及空间速度权衡。此选项既增加编译时间，提高了生成代码的性能。
    #OPT = -O3        ## 优化更多。
    #OPT = -Os        ## 优化大小。启用所有优化，除了那些经常增加代码大小
    #OPT = -Ofast     ## 不遵守严格的标准。使所有优化。它还支持并非对所有符合标准的程序都有效的优化。
]]
#  编译参数
target_compile_options(${ELF_TARGET} PRIVATE
    ${CPU_FLAGS}
    -Wall
    -Wextra
    -g 
    -gdwarf-2 
    -mthumb 
    -Os 
    -fdata-sections 
    -ffunction-sections
    -fmessage-length=0
)
# 链接参数
target_link_options(${ELF_TARGET} PRIVATE
    -T${LINK_SCRIPT}
    ${CPU_FLAGS}
    --specs=nano.specs
    -Wl,--gc-sections
    -Wl,-Map=${CMAKE_PROJECT_NAME}.map
    -Wl,--cref
    -Wl,--print-memory-usage
)


# 添加自定义命令，用于生成 hex bin 格式文件，POST_BUILD 表示生成 TARGET 后执行命令
add_custom_command(TARGET ${ELF_TARGET} POST_BUILD
    COMMAND ${CMAKE_OBJCOPY} -O ihex ${ELF_TARGET} ${CMAKE_PROJECT_NAME}.hex
    COMMAND ${CMAKE_OBJCOPY} -O binary -S ${ELF_TARGET} ${CMAKE_PROJECT_NAME}.bin
)
